Ana içeriğe geç
  1. 100 Günde SwiftUI Notları/

9.Gün - Swift Fonksiyonlar - 3 : Closure Nedir? Nasıl Çalışır? Closure Örnekleri

Fonksiyonun Değişkene Atanması #

Fonksiyonları değişkenlere atayabilir, fonksiyonları fonksiyonlara parametre olarak geçebilir veya fonksiyonlardan fonksiyonları döndürebiliriz.

func greetUser() {
    print("Hi there!")
}

greetUser()

var greetCopy = greetUser
greetCopy()

//ÇIKTI:
//----------------------------------------
// Hi there!
// Hi there!

Yukarıdaki örnekte bir fonksiyonun kendisi, bir değişkene atanmıştır.

Önemli : Fonksiyon, değişkene atanırken () -parantezler- kullanılmaz. Parantezleri kullandığımızda fonksiyonu çağırmış oluruz ve fonksiyonun dönüş değeri değişkene atanır.

Closure Tanımlanması #

Yukarıda fonksiyonun kendisinin bir değişken (veya sabit)’e atanabileceğini gördük. Peki ya fonksiyon tanımlama adımını atlayıp, fonksiyonun yaptığı işi doğrudan bir değişkene atayamaz mıyız? İşte Swift bu işleme closure adını vermekte.

let sayHello = {
    print("Hi there!")
}

sayHello()

//ÇIKTI:
//----------------------------------------
// Hi there!

Yukarıda closure kullanılarak oluşturulmuş bir fonksiyon ve bu fonksiyonun atandığı bir sabit görüyoruz. Fonksiyon çağrısını yapabilmek için sabitin () parantezlerini kullanarak yazıldığına dikkat edelim. Yukarıdaki closure herhangi bir parametre almamış ve geri dönüş değeri de yoktur. Şimdide bunları barındıran bir closure yazalım.

let sayHello = { (name: String) -> String in
    "Hi \(name)!"
}

closure, { } parantezleri ile başlar ve biter. Bu sebeple parametreleri ve geri dönüş değer tipini bildirdiğimiz yer de bu parantezler içi olmalıdır. Yukarıdaki örnekte gördüğümüz in anahtar kelimesi ise parametre ve geri dönüş tipi bildiriminin sonunu işaretlemek için kullanılır.

Parametre Almayan, Değer Döndüren Closure Tanımlanması #

let payment = { () -> Bool in
    print("Paying an anonymous person…")
    return true
}

Closure ‘ın hiç parametre almadığını belirtmek için () boş parantezleri kullanırız. func payment() -> Bool yazmaya oldukça benziyor.

Fonksiyonların Türü #

Swift’te, tamsayıların Int türüne ondalık sayıların Double türüne sahip olduğu gibi, fonksiyonlar da bir türe (type) sahiptir.

Yukarılardaki örnekte yazdığımız parametre almayan ve bir geri dönüşü bulunmayan greetUser() fonksiyonunu type annotation kullanarak greetCopy değişkenine atayalım.

var greetCopy: () -> Void = greetUser

Yukarıdaki kodu biraz inceleyelim;

  1. Boş parantezler () parametre almayan bir fonksiyonu işaret eder.
  2. -> fonksiyon oluştururken kullanılan anlamdadır, yani geri dönüş türünü bildireceğiz.
  3. Void hiçbir şey anlamına gelir. Yani bu fonksiyonun geri dönüş değeri yoktur. Bazen Void yerine () yazıldığını görebiliriz.

Her fonksiyonun türü, aldığı ve geri gönderdiği verilere bağlıdır. Burada önemli bir püf noktası var: aldığı verilerin isimleri, fonksiyonun türünün bir parçası değildir. Bir örnek daha yapalım;

func getUserData(for id: Int) -> String {
    if id == 1989 {
        return "Taylor Swift"
    } else {
        return "Anonymous"
    }
}

let data: (Int) -> String = getUserData
let user = data(1989)
print(user)

Int türünde bir veri alan, geri dönüş tipi String olan getUserData fonksiyonunu , data sabitine atayalım. data fonksiyonunu çağırırken, data(for: 1989) yazmak yerine, data(1989) yazdık. Çünkü fonksiyon türü harici parametre adını içermez.

Bu durum closure için de geçerlidir. Daha önce tanımladığımız sayHello closure ‘unun çağrısını şimdi yapalım.

let sayHello = { (name: String) -> String in
    "Hi \(name)!"
}

sayHello("Taylor")
//ÇIKTI:
//----------------------------------------
// Hi Taylor!

sayHello closure ‘ı tıpkı fonksiyonların kopyalanmasında olduğu gibi, parametre adı kullanmaz. Yani harici parametre adları yalnızca bir fonksiyonu doğrudan çağırdığımızda önemlidir, bir closure oluşturduğumuzda veya fonksiyonun kopyasını aldığımızda değil.

Closure Kullanımı #

let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]
let sortedTeam = team.sorted()
print(sortedTeam)

team Array’i harika bir şekilde sıralanacaktır. Fakat bu sıralamayı kontrol etmek istesek nasıl yapacağız? Örneğin, takım kaptanının adının en başta olmasını, ardından isimlerin alfabetik olarak sıralanmasını isteyelim. İşte sorted() bu durumu kontrol etmek için özel bir sıralama fonksiyonu geçmemize izin verir. Bu fonksiyon, iki adet String ’i parametre olarak alır ve eğer ilk String daha önce sıralanıyorsa true değilse false döndürür. Eğer Suzanne kaptansa yazacağımız kod şu şekilde olmalıdır.

func captainFirstSorted(name1: String, name2: String) -> Bool {
    if name1 == "Suzanne" {
        return true
    } else if name2 == "Suzanne" {
        return false
    }

    return name1 < name2
}

Yani name1 Suzanne ise true dönecek çünkü name1 name2 ’den önce geldiği için true olmalıdır. Fakat name2 Suzanne ise false dönecek çünkü, name1 name2 ’den sonraya sıralanmalıdır. Eğer hiçbir isim Suzanne değilse, < ile normal alfabetik sıralama yapılacaktır.

Bu sayede captainFirstSorted fonksiyonunu, sorted() fonksiyonuna parametre olarak geçebiliriz.

let captainFirstTeam = team.sorted(by: captainFirstSorted)
print(captainFirstTeam)
//ÇIKTI:
//----------------------------------------
// ["Suzanne", "Gloria", "Piper", "Tasha", "Tiffany"]

sorted() fonksiyonu iki String kabul eden ve bir Boolean dönen bir fonksiyon ister. Bu fonksiyonun func kullanılarak mı tanımlandığı yoksa bir closure ‘mı olduğunun bir önemi yoktur.

sorted() fonksiyonunu closure kullanarak yeniden yazacağız.

let captainFirstTeam = team.sorted(by: { (name1: String, name2: String) -> Bool in
    if name1 == "Suzanne" {
        return true
    } else if name2 == "Suzanne" {
        return false
    }

    return name1 < name2
})

Yukarıdaki kodu biraz inceleyelim;

  • Daha önce olduğu gibi sorted() fonksiyonunu çağırıyoruz.
  • Bir fonksiyon geçmek yerine, bir closure yazıyoruz.
  • sorted() fonksiyonunun bize aktaracağı iki String parametresini listeliyoruz, closure’ın bir Boolean döndüreceğini söylüyor ve closure kodunun başlangıcını in kullanarak işaretliyoruz.
  • Geri kalan her şey normal fonksiyon kodudur.

Closure Kısa Syntax #

Yukarıda oluşturduğumuz kodu biraz daha derli toplu tekrar buraya yazalım.

let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]

let captainFirstTeam = team.sorted(by: { (name1: String, name2: String) -> Bool in
    if name1 == "Suzanne" {
        return true
    } else if name2 == "Suzanne" {
        return false
    }

    return name1 < name2
})

print(captainFirstTeam)

sorted() fonksiyonu için iki String alan ve bir Boolean döndüren bir fonksiyon sağlanmalıdır. Öyleyse closure içinde neden aynı şeyleri tekrarlıyoruz.

Closure ‘ı kısa olarak yazmak için, alınan parametre ve geri dönüş türünü belirtmemize gerek yok.

Bu sebeple kodu şu şekilde yazabiliriz.

let captainFirstTeam = team.sorted(by: { name1, name2 in

Şimdi kodu biraz daha azaltmamızın bir yolu var. sorted() gibi fonksiyon kabul eden fonksiyonlar trailing closure syntax olarak adlandırılan yazım biçimine izin verir.

let captainFirstTeam = team.sorted { name1, name2 in
    if name1 == "Suzanne" {
        return true
    } else if name2 == "Suzanne" {
        return false
    }

    return name1 < name2
}

Trailing closure syntax sayesinde, closure’ı parametre olarak geçmek yerine, devam edip closure ‘ı başlatıyoruz. Bunu yaparken, sorted fonksiyonunun () parantezlerini ve parametre ismini kaldırıyoruz.

Swift’te closure ‘u sadeleştirebilecek son bir adım daha var. Swift closure içindeki parametre adlarını otomatik olarak sağlayabilir. Bu durumda name1 ve name2 yerine $0 ve $1 kullanabiliriz.

let captainFirstTeam = team.sorted {
    if $0 == "Suzanne" {
        return true
    } else if $1 == "Suzanne" {
        return false
    }

    return $0 < $1
}

Closure Kullanımı ile İlgili Örnekler #

sorted() fonksiyonu ile bu sefer sırlamayı tersine çevirelim.

let reverseTeam = team.sorted { $0 > $1 }

filter() fonksiyonu ile her bir Array elemanı üzerinde bazı kontroller yapabiliriz. Bu kontroller neticesinde true olan öğeler yeni bir Array olarak döndürülür. Örneğin, takımdaki oyunculardan isminin baş harfi T olan oyuncuları şu şekilde alabiliriz.

let tOnly = team.filter { $0.hasPrefix("T") }
print(tOnly)
//ÇIKTI:
//----------------------------------------
// ["Tiffany", "Tasha"]

map() fonksiyonu, Array’de bulunan her bir elemanı dönüştürmemizi sağlar. İsteğimize göre dönüştürülmüş Array’i geri döndürür. Aşağıdaki örnekte Array’deki tüm elemanların büyük harfli hali geri dönüş olarak alınmıştır.

let uppercaseTeam = team.map { $0.uppercased() }
print(uppercaseTeam)

//ÇIKTI:
//----------------------------------------
// ["GLORIA", "SUZANNE", "PIPER", "TIFFANY", "TASHA"]

Not: map() fonksiyonu ile çalışırken, döndürdüğümüz türün, başladığımız tür ile aynı olması gerekmez. Örneğin, Int olarak aldığımız veriyi String olarak geri döndürebiliriz.

Fonksiyonlar Parametre Olarak Nasıl Kabul Edilir? #

Closure kullanılabilmesi için, fonksiyonun parametre olarak fonksiyon alabilmesi gerekmektedir. Bu başlık altında bunun nasıl yapılabileceğini inceleyeceğiz.

Daha önce aşağıdaki kodu görmüştük;

func greetUser() {
    print("Hi there!")
}

greetUser()

var greetCopy: () -> Void = greetUser
greetCopy()

greetCopy değişkeni tanımlanırken type annotation kasıtlı olarak eklenmiştir. Çünkü fonksiyonları parametre olarak belirtirken tam olarak bunu kullanıyoruz. Swift’e fonksiyonun hangi parametreleri kabul ettiğini ve dönüş tipini söylüyoruz.

func makeArray(size: Int, using generator: () -> Int) -> [Int] {
    var numbers = [Int]()

    for _ in 0..<size {
        let newNumber = generator()
        numbers.append(newNumber)
    }

    return numbers

Yukarıdaki kodu inceleyelim;

  1. Fonksiyonun adı makeArray() Biri Int olmak üzere iki parametre alır ve bir Int Array döndürür.
  2. İkinci parametre bir fonksiyondur. Parametre olan fonksiyonun kendisi parametre kabul etmez, ancak her çağrıldığında bir tamsayı döndürür.
  3. makeArray() içinde boş bir Int Array oluştururuz ve istenildiği kadar döngü yaparız.
  4. Döngünün her yinelenmesinde, parametre olarak aktarılan generator fonksiyonunu çağrırız. Bu da bize yeni bir tamsayı döndürecektir. Bu dönen tamsayıyı numbers Array’ine ekleriz.
  5. En sonunda tamamlanan Array’i geri döndürürüz.

Buradaki en karmaşık kısım aslında ilk satırdır:

func makeArray(size: Int, using generator: () -> Int) -> [Int] {

Yukarıdaki kodu soldan itibaren okursak;

  1. Yeni bir fonksiyon oluştururuz.
  2. Bu fonksiyonun adı makeArray() ’dir.
  3. İlk parametre bir Int olan size ’dır.
  4. İkinci parametre, kendisi parametre kabul etmeyen ve bir tamsayı döndüren generator adlı fonksiyondur.
  5. Sonuç olarak makeArray() Int Array’i döndürür.

Yukarıda oluşturduğumuz fonksiyonu kullanalım

let rolls = makeArray(size: 50) {
    Int.random(in: 1...20)
}

print(rolls)

Yukarıdaki kodu closure kullanmadan şu şekilde de yazabiliriz.

func generateNumber() -> Int {
    Int.random(in: 1...20)
}

let newRolls = makeArray(size: 50, using: generateNumber)
print(newRolls)

Birden Fazla Parametre Olarak Fonksiyon Alan Fonksiyonlar #

Bu durumu göstermek için, her biri parametre kabul etmeyen ve hiçbir şey geri döndürmeyen parametre olarak fonksiyon alan bir fonksiyon oluşturalım

func doImportantWork(first: () -> Void, second: () -> Void, third: () -> Void) {
    print("About to start first work")
    first()
    print("About to start second work")
    second()
    print("About to start third work")
    third()
    print("Done!")
}

Bu fonksiyonu closure ile çağırmak istediğimizde, ilk çağrılacak closure daha önce yaptığımız gibi çağrılır. Fakat ikinci ve üçüncü closure için harici parametre adını yazıp ardından closure açmamız gerekir.

doImportantWork {
    print("This is the first work")
} second: {
    print("This is the second work")
} third: {
    print("This is the third work")
}

//ÇIKTI:
//----------------------------------------
//About to start first work
//Doing first thing
//About to start second work
//Doing second thing
//About to start third work
//Doing third thing
//Done!

Sonuç #

Bu yazıda Swift Fonksiyonlarının son bölümü olan Closure konusunu tamamladık. Özetlemek gerekirse;

  • Swift’te fonksiyonları kopyalayabiliriz, harici parametre adlarını kaybetmeleri dışında orijinaliyle aynı şekilde çalışırlar.
  • Tüm fonksiyonların veri türü vardır. (Tamsayının Int , ondalıklı sayının Double olması gibi). Bu veri türü, aldıkları parametrelerin tipi ve dönüş tipidir.
  • Bir sabite veya değişkene atama yaparak closure oluşturabiliriz.
  • Parametre alan veya bir değer döndüren closure ‘larda bu işlem { } parantezleri içinde yapılmalı ve in ile işaretlenmelidir.
  • Fonksiyonlar, diğer fonksiyonları parametre olarak kabul edebilir. Parametre olan fonksiyonların tam olarak hangi değeleri alıp döndüreceği belirlenmelidir.
    • Bu durumda özel bir fonksiyonu geçmek yerine bir closure oluşturabiliriz. Swift her iki yaklaşımı da kabul eder.
  • Bir fonksiyonun son parametrelerinden biri veya daha fazlası fonksiyon ise, trailing closure syntax kullanabiliriz.
  • Closure içinde $0 ve $1 gibi kısaltılmış parametre adaları kullanabiliriz.

100 Days of SwiftUI Checkpoint - 5 #


Bu yazıyı İngilizce olarak da okuyabilirsiniz.
You can also read this article in English.

Bu yazı, SwiftUI Day 9 adresinde bulunan yazılardan kendim için aldığım notları içermektedir. Orjinal dersi takip etmek için lütfen bağlantıya tıklayın.